package cn.legym.garp.gateway.traffic.dynamic;

import cn.legym.garp.gateway.traffic.config.TrafficDataSourceListener;
import cn.legym.garp.gateway.traffic.dynamic.bean.DynamicResourceDefinition;
import cn.legym.garp.gateway.traffic.dynamic.context.PropertyUpdateEventQueue;
import cn.legym.garp.gateway.traffic.rule.TrafficRule;
import com.alibaba.cloud.sentinel.datasource.RuleType;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.property.SentinelProperty;
import com.alibaba.csp.sentinel.slots.block.AbstractRule;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
import com.alibaba.fastjson.JSON;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.BinaryOperator;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * create by pipisun on 2021/9/7
 * copyright © 2017-2021 Legym Technology Co.,Ltd. All rights reserve
 * d.
 * file description:
 * last update by {} on {}
 * update description:
 */
@Component
@Slf4j
@Order
public final class DynamicResourceManager implements SmartInitializingSingleton,
        TrafficDataSourceListener<DynamicResourceDefinition>, SentinelDatasourceWatcher.InternalSentinelListener {

    @Autowired
    private ApplicationContext context;
    @Autowired
    private SentinelDatasourceWatcher sentinelDatasourceWatcher;

    private final Map<String, DynamicResourceDefinition> resourceDefinitions = Maps.newConcurrentMap();
    private final Cache<String, Object> resourceWorkingCache = CacheBuilder.newBuilder()
            .expireAfterAccess(1800, TimeUnit.SECONDS)
//            .maximumSize(9999)
            .build();

    public boolean locateResource(ServerHttpRequest request, TrafficRule rule) {
        DynamicResourceDefinition definition = resourceDefinitions.get(rule.getResource());
        if (definition == null) {
            return false;
        }
        DynamicResourceDefinition.MatchResult result = definition.matchRequest(context, request);
        if (result == null || !result.isSuccess()) {
            return false;
        }
        String dyResourceName = dynamicResourceName(definition, result);
        try {
            // get from cache or load+cache
            resourceWorkingCache.get(dyResourceName, () -> {
                RuleFunctionEnum funcEnum = RuleFunctionEnum.fromRuleType(definition.getMeta().getRuleType());
                if (funcEnum == null) {
                    throw new RuntimeException("resource not found due to an unknown ruleType:" + definition.getMeta().getRuleType());
                }
                Object exists = funcEnum.findExistingRuleByName(dyResourceName);
                if (exists != null) {// 在Sentinel规则中，不在缓存中。加入缓存
                    return exists;
                }
                Object created = createDynamicResource(dyResourceName, definition, funcEnum);
                if (created != null) {// 创建新规则，加入Sentinel本地资源
                    return created;
                }
                throw new RuntimeException("resource load fail. name=" + dyResourceName);
            });
            return true;
        } catch (ExecutionException e) {
            log.error("", e);
        }
        return false;
    }

    private String dynamicResourceName(DynamicResourceDefinition definition, DynamicResourceDefinition.MatchResult result) {
        return String.format("$dynamic_%s$%s$%s", definition.getName(),
                definition.getMeta().getRuleType(), result.getResults().stream()
                        .map(r -> r.getKey() + "=" + r.getValue())
                        .reduce("", (s1, s2) -> s1 + "#" + s2));
    }

    private Object createDynamicResource(String name, DynamicResourceDefinition definition, RuleFunctionEnum functionEnum) {
        SentinelProperty<?> property = sentinelDatasourceWatcher.getSourceByType(functionEnum.ruleType);
        if (property == null) {
            return null;
        }
        return functionEnum.invokeUpdateRule(name, definition.getMeta().getContent(), property);
    }

    @Override
    public void afterSingletonsInstantiated() {

    }

    /**
     * Sentinel规则数据变更通知
     * <p>
     * 加载本地规则到Sentinel
     *
     * @param ruleType 发生更改的Sentinel规则类型
     * @param value    发生更改后的Sentinel规则数据
     */
    @Override
    public void onChange(RuleType ruleType, Object value) {
        if (!(value instanceof Collection)) {
            return;
        }
        if (resourceWorkingCache.size() <= 0) {
            return;
        }
        // 创建event，排队触发。
        // 事件插入队列，等待SentinelProperty的全部listener执行之后触发。
        PropertyUpdateEventQueue.push(() -> {
            RuleFunctionEnum funcEnum = RuleFunctionEnum.fromRuleType(ruleType);
            Class<?> elementClazz = ruleType.getClazz();

//            Collection<?> collection = (Collection<?>) value;
            Collection<?> collection = funcEnum.ruleSupplier.get();
            Set<String> currentRuleNames = collection.stream().map(funcEnum::invokeRuleNameSupplier)
                    .filter(Objects::nonNull).collect(Collectors.toSet());

            // 组装本地规则，加入Sentinel
            List<Object> localRules = resourceWorkingCache.asMap().values().stream()
                    .filter(rule -> elementClazz.isAssignableFrom(rule.getClass()))
                    .filter(rule -> !currentRuleNames.contains(funcEnum.invokeRuleNameSupplier(rule)))
                    .collect(Collectors.toList());
            if (localRules.isEmpty()) {
                return;
            }
            SentinelProperty<?> property = sentinelDatasourceWatcher.getSourceByType(ruleType);
            if (property == null) {
                return;
            }
            funcEnum.invokeUpdateRules(collection, localRules, property);
        });
    }


    /**
     * DynamicResourceDefinition 内容发生变更通知
     *
     * @param source 新的定义集合
     */
    @Override
    public void updateData(Collection<DynamicResourceDefinition> source) {
        if (source == null) {
            return;
        }
        Map<String, DynamicResourceDefinition> map = source.stream()
                .collect(Collectors.toMap(DynamicResourceDefinition::getName, r -> r,
                        BinaryOperator.minBy(Comparator.comparingInt(DynamicResourceDefinition::hashCode))));
        resourceDefinitions.putAll(map);
        resourceDefinitions.keySet().stream().filter(k -> !map.containsKey(k)).forEach(resourceDefinitions::remove);
        // 重新构建缓存
        resourceWorkingCache.invalidateAll();
        // Sentinel 规则失效
        Arrays.stream(RuleFunctionEnum.values()).forEach(e -> {
            SentinelProperty<?> property = sentinelDatasourceWatcher.getSourceByType(e.ruleType);
            if (property == null) {
                return;
            }
            e.invalidateLocalRule(property);
        });
    }

    public enum RuleFunctionEnum {
        FLOW(RuleType.FLOW, FlowRuleManager::getRules, List.class, "resource"),
        DEGRADE(RuleType.DEGRADE, DegradeRuleManager::getRules, List.class, "resource"),
        PARAM_FLOW(RuleType.PARAM_FLOW, ParamFlowRuleManager::getRules, List.class, "resource"),
        SYSTEM(RuleType.SYSTEM, SystemRuleManager::getRules, List.class, "resource"),
        AUTHORITY(RuleType.AUTHORITY, AuthorityRuleManager::getRules, List.class, "resource"),
        GW_FLOW(RuleType.GW_FLOW, GatewayRuleManager::getRules, Set.class, "resource"),
        GW_API_GROUP(RuleType.GW_API_GROUP, GatewayApiDefinitionManager::getApiDefinitions, Set.class, "apiName"),
        ;
        private final RuleType ruleType;
        private final Supplier<Collection<?>> ruleSupplier;
        private final Class<?> ruleCollectionType;

        private Field ruleNameField;

        private static final Map<RuleType, RuleFunctionEnum> ruleTypeMapping = Maps.newHashMap();

        static {
            ruleTypeMapping.putAll(Arrays.stream(values()).collect(Collectors.toMap(obj -> obj.ruleType, obj -> obj)));
        }

        RuleFunctionEnum(RuleType ruleType, Supplier<Collection<?>> getRules, Class<?> ruleCollectionType, String ruleNameGetter) {
            this.ruleType = ruleType;
            this.ruleSupplier = getRules;
            this.ruleCollectionType = ruleCollectionType;
            try {
                Class<?> clazz = this.ruleType.getClazz();
                this.ruleNameField = AbstractRule.class.isAssignableFrom(clazz) ?
                        clazz.getSuperclass().getDeclaredField(ruleNameGetter) : clazz.getDeclaredField(ruleNameGetter);
                this.ruleNameField.setAccessible(true);
            } catch (Exception e) {
                log.error("", e);
            }
            Assert.notNull(this.ruleNameField, "ruleNameField must not be NULL. RuleType=" + ruleType);
        }

        public Object findExistingRuleByName(String ruleName) {
            if (ruleName == null || ruleName.isEmpty()) {
                return null;
            }
            Collection<?> rules = ruleSupplier.get();
            for (Object o : rules) {
                String name = invokeRuleNameSupplier(o);
                if (name != null && name.equals(ruleName)) {
                    return o;
                }
            }
            return null;
        }

        public String invokeRuleNameSupplier(Object obj) {
            try {
                Object result = ruleNameField.get(obj);
                if (result == null) {
                    return null;
                }
                return result.toString();
            } catch (Exception e) {
                log.error("", e);
            }
            return null;
        }

        /**
         * 增加一条本地规则到Sentinel
         *
         * @param name        规则名称
         * @param rawRuleJson 规则JSON
         * @param property    sentinel数据源
         * @return 新增的规则Object
         */
        public Object invokeUpdateRule(String name, String rawRuleJson, SentinelProperty<?> property) {
            try {
                Collection rules = ruleSupplier.get();
                Object parseObject = JSON.parseObject(rawRuleJson, ruleType.getClazz());
                // set name
                this.ruleNameField.set(parseObject, name);
                rules.add(parseObject);
                updateProperty(property, rules);
                return parseObject;
            } catch (Exception e) {
                log.error("", e);
            }
            return null;
        }

        /**
         * 更新全部相关本地规则到sentinel
         *
         * @param sentinelRules sentinel持有的规则
         * @param localRules    本地规则集合
         * @param property      sentinel数据源
         */
        public void invokeUpdateRules(Collection sentinelRules, List<Object> localRules, SentinelProperty<?> property) {
            try {
//                Collection rules = ruleSupplier.get();
//                rules.addAll(allRules);
                List<Object> rules = Lists.newArrayList();
                rules.addAll(sentinelRules);
                rules.addAll(localRules);
                updateProperty(property, rules);
            } catch (Exception e) {
                log.error("", e);
            }
        }

        /**
         * 本地规则失效，从Sentinel中删除
         *
         * @param property sentinel 数据源
         */
        public void invalidateLocalRule(SentinelProperty<?> property) {
            try {
                Collection<?> rules = ruleSupplier.get();
                List<Object> list = rules.stream()
                        .filter(rule -> {
                            String name = this.invokeRuleNameSupplier(rule);
                            return name != null && name.startsWith("$dynamic");
                        }).collect(Collectors.toList());
                rules.removeAll(list);
                updateProperty(property, rules);
            } catch (Exception e) {
                log.error("", e);
            }
        }

        private void updateProperty(SentinelProperty<?> property, Collection<?> rules) throws Exception {
//            Method method = SentinelProperty.class.getMethod("updateValue", ruleCollectionType);
            Method method = property.getClass().getMethod("updateValue", Object.class);
            if (Set.class.equals(ruleCollectionType)) {
                rules = new HashSet<>(rules);
            }
            method.invoke(property, rules);
        }

        public static RuleFunctionEnum fromRuleType(RuleType ruleType) {
            return ruleTypeMapping.get(ruleType);
        }
    }
}